/*
 * Copyright 2008-2009 Aying.Net Powered by Fuchun.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.aying.text;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.Locale;
import java.util.ArrayList;
import java.util.Collections;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.aying.util.Utils;

/**
 * @author Fuchun
 * @version 1.0, 2009-7-5
 */
public class LocalizedTextUtils extends Utils {

    private static final Log log = LogFactory.getLog(LocalizedTextUtils.class);

    /** 表示资源包不存在的常量。 */
    private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {

        public Enumeration<String> getKeys() {
            return null;
        }

        protected Object handleGetObject(String key) {
            return null;
        }

        public String toString() {
            return "NONEXISTENT_BUNDLE";
        }
    };

    private static final List<String> resourceBundles;

    private static final ConcurrentMap<String, ResourceBundle> bundlesMap;

    private static final Map<MessageFormatKey, MessageFormat> messageFormats;

    private static ClassLoader delegatedClassLoader;

    /* 是否重新载入资源包 */
    private static boolean reloadResourceBundles = false;

    static {
        resourceBundles = Collections.synchronizedList(new ArrayList<String>());
        bundlesMap = new ConcurrentHashMap<String, ResourceBundle>();
        messageFormats = new HashMap<MessageFormatKey, MessageFormat>();
        initDefaultResourceBundle();
    }

    /**
     * 初始化默认的资源包。
     */
    public static void initDefaultResourceBundle() {
        if (resourceBundles != null) {
            synchronized (resourceBundles) {
                resourceBundles.clear();
                resourceBundles.add("net.aying.text.fc-webtools");
            }
        } else {
            synchronized (resourceBundles) {
                resourceBundles.add("net.aying.text.fc-webtools");
            }
        }
    }

    /**
     * 设置是否每次都会重新载入资源包。
     * 
     * @param reloadResourceBundles 是否每次都会重新载入资源包。
     */
    public static void setReloadResourceBundles(boolean reloadResourceBundles) {
        LocalizedTextUtils.reloadResourceBundles = reloadResourceBundles;
    }

    /**
     * 查找给定基本名称和语言环境的资源包，并将其放入缓存中保存。
     * <p />
     * 默认使用 {@code Thread.currentThread().getContextClassLoader()} 作为类加载器加载资源包。
     * 如果当前线程中的上下文 {@code ClassLoader} 不可用，并且设置了 {@code delegatedClassLoader}
     * 属性， 则委托给 {@code delegatedClassLoader} 类加载器。
     * 
     * @param bundleName 资源包的基本名称，通常是一个完全限定类名。
     * @param locale 资源包所需的语言环境。
     * @return 具有给定基本名称和语言环境的资源包。若不存在，则返回 {@code null}。
     */
    public static ResourceBundle findResourceBundle(String bundleName, Locale locale) {
        String key = createMisseskey(bundleName, locale);

        ResourceBundle bundle = null;
        try {
            if (!bundlesMap.containsKey(key)) {
                bundle = ResourceBundle.getBundle(bundleName, locale, Thread.currentThread()
                        .getContextClassLoader());
                bundlesMap.put(key, bundle);
            }
            bundle = bundlesMap.get(key);
        } catch (MissingResourceException ex) {
            if (delegatedClassLoader != null) {
                try {
                    if (!bundlesMap.containsKey(key)) {
                        bundle = ResourceBundle.getBundle(bundleName, locale, delegatedClassLoader);
                        bundlesMap.put(key, bundle);
                    }
                    bundle = bundlesMap.get(key);
                } catch (MissingResourceException e) {
                    bundle = NONEXISTENT_BUNDLE;
                    bundlesMap.put(key, bundle);
                }
            } else {
                bundle = NONEXISTENT_BUNDLE;
                bundlesMap.put(key, bundle);
            }
        }
        return bundle == NONEXISTENT_BUNDLE ? null : bundle;
    }

    /**
     * 设置用于查找资源包的类加载器。
     * 
     * @param classLoader 要设置的 类加载器。
     */
    public static void setDelegatedClassLoader(final ClassLoader classLoader) {
        synchronized (bundlesMap) {
            delegatedClassLoader = classLoader;
        }
    }

    /**
     * 移除给定资源包名的缓存快照。
     * 
     * @param bundleName 要删除的资源包名。
     */
    public static void removeBundle(final String bundleName) {
        synchronized (bundlesMap) {
            bundlesMap.remove(bundleName);
        }
    }

    public static String findText(Class<?> clazz, String textName, Locale locale,
            String defaultText, Object... args) {
        String indexedTextName = null;
        if (textName == null) {
            log.warn("要查找的资源信息的 key 是一个 null 值！");
            textName = "";
        }
        if (textName.contains("[")) {
            int i = -1;
            indexedTextName = textName;
            while ((i = indexedTextName.indexOf("[", i + 1)) != -1) {
                int j = indexedTextName.indexOf("]", i);
                String a = indexedTextName.substring(0, i);
                String b = indexedTextName.substring(j);
                indexedTextName = a + "[*" + b;
            }
        }

        String message = findMessage(clazz, textName, locale, args);
        if (message != null) {
            return message;
        }

        for (Class<?> c = clazz; (clazz != null) && !clazz.equals(Object.class); c = c
                .getSuperclass()) {
            String basePackageName = c.getName();
            while (basePackageName.lastIndexOf(".") != -1) {
                basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf("."));
                String packageName = basePackageName + ".package";
                message = getMessage(packageName, locale, textName, args);
                if (message != null) {
                    return message;
                }
            }
        }
        return defaultText;
    }

    /**
     * 重置并清空所有的保存信息的缓存。
     */
    public static void reset() {
        initDefaultResourceBundle();
        bundlesMap.clear();
        synchronized (messageFormats) {
            messageFormats.clear();
        }
    }

    private static String findMessage(Class<?> clazz, String key, Locale locale, Object... args) {
        String message = getMessage(clazz.getName(), locale, key, args);
        if (message != null) {
            return message;
        }

        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class<?> aInterface : interfaces) {
            message = getMessage(aInterface.getName(), locale, key, args);
            if (message != null) {
                return message;
            }
        }
        if (clazz.isInterface()) {
            interfaces = clazz.getInterfaces();
            for (Class<?> aInterface : interfaces) {
                message = findMessage(aInterface, key, locale, args);
                if (message != null) {
                    return message;
                }
            }
        } else {
            if (clazz.equals(Object.class) && !clazz.isPrimitive()) {
                return findMessage(clazz.getSuperclass(), key, locale, args);
            }
        }
        return null;
    }

    /**
     * 根据资源包名称找到资源包，并获得给定键的文本信息。若没有找到给定键或给定键的信息，则返回 {@code null}。
     * 
     * @param bundleName 给定的资源包名称（全限定名称）。
     * @param locale 资源包所需的语言环境。
     * @param key 给定的 {@code key}。
     * @param args 资源信息的参数。
     * @return 给定键的文本信息。
     */
    private static String getMessage(String bundleName, Locale locale, String key, Object... args) {
        ResourceBundle bundle = findResourceBundle(bundleName, locale);
        if (bundle == null) {
            return null;
        }
        reloadBundles();

        try {
            String message = bundle.getString(key);
            MessageFormat format = buildMessageFormat(message, locale);
            return format.format(args);
        } catch (MissingResourceException ex) {
            return null;
        }
    }

    /* 创建缓存快照 Key */
    private static String createMisseskey(String bundleName, Locale locale) {
        return bundleName + "_" + locale.toString();
    }

    /* 构建 MessageFormat 对象。 */
    private static MessageFormat buildMessageFormat(String pattern, Locale locale) {
        MessageFormatKey formatKey = new MessageFormatKey(pattern, locale);
        MessageFormat format = null;
        synchronized (messageFormats) {
            format = messageFormats.get(formatKey);
            if (format == null) {
                format = new MessageFormat(pattern);
                format.setLocale(locale);
                format.applyPattern(pattern);
                messageFormats.put(formatKey, format);
            }
        }
        return format;
    }

    /* 重新加载资源包？ */
    private static void reloadBundles() {
        if (reloadResourceBundles) {
            try {
                clearMap(ResourceBundle.class, null, "cacheList");

                clearTomcatCache();
            } catch (Exception ex) {
                log.error("不能载入资源包！", ex);
            }
        }
    }

    /* 清空 Tomcat 的缓存！ */
    private static void clearTomcatCache() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class<?> c = loader.getClass();
        try {
            if ("org.apache.catalina.loader.WebappClassLoader".equals(c.getName())) {
                clearMap(c, loader, "resourceEntries");
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("当前线程的类加载器 " + c.getName() + " 不是 Tomcat 线程类加载器！");
                }
            }
        } catch (Exception ex) {
            log.warn("不能清空 Tomcat 的缓存！", ex);
        }
    }

    private static <T> void clearMap(Class<T> clazz, Object object, String name)
            throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
            InvocationTargetException {
        Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);

        Object cache = field.get(object);

        synchronized (cache) {
            Class<?> c = cache.getClass();
            Method clearMethod = c.getMethod("clear");
            clearMethod.invoke(cache);
        }
    }

    static class MessageFormatKey {

        String pattern;

        Locale locale;

        MessageFormatKey(String pattern, Locale locale) {
            this.pattern = pattern;
            this.locale = locale;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof MessageFormatKey)) return false;

            final MessageFormatKey formatKey = (MessageFormatKey) o;
            if (locale != null ? !locale.equals(formatKey.locale) : formatKey.locale != null)
                return false;
            if (pattern != null ? !pattern.equals(formatKey.pattern) : formatKey.pattern != null)
                return false;
            return true;
        }

        @Override
        public int hashCode() {
            int result;
            result = (pattern != null) ? pattern.hashCode() : 0;
            result = 29 * result + (locale != null ? locale.hashCode() : 0);
            return result;
        }
    }
}
